﻿using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Unity;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Windows.Input;
using System.Windows;
using System.Windows.Data;

namespace Reckoning
{
    /// <summary>
    /// Model for Main view
    /// </summary>
    public class MainViewModel: NotifyPropertyChangedBase
    {
        #region Properties       

        // commands
        public ICommand ComputeCommand
        {
            get
            {
                return _computeCommand;
            }
        }

        public ICommand EditStopWordsCommand
        {
            get
            {
                return _editStopWordsCommand;
            }
        }

        public ICommand SaveAsReferenceCommand
        {
            get
            {
                return _saveAsReferenceCommand;
            }
        }

        private readonly ICommand _computeCommand;
        private readonly ICommand _saveAsReferenceCommand;
        private readonly ICommand _editStopWordsCommand;
        
        /// <summary>
        /// Object that will do the raw counting
        /// </summary>
        private ICounter _counter;

        /// <summary>
        /// Object that will filter the count
        /// </summary>
        private ICountFilter _filter;

        /// <summary>
        /// Dialog service
        /// </summary>
        private IFileDialogService _dlgService;

        /// <summary>
        /// Model for stop words view
        /// </summary>
        private IStopWordsViewModel _stopWordsModel;

        /// <summary>
        /// Whether the work must be re-run due to a change in variables
        /// </summary>
        private bool _forceReRun;

        /// <summary>
        /// Raw word counts
        /// </summary>
        private Dictionary<string, uint> _rawCounts;

        /// <summary>
        /// The path to the file that contains the text to be analyzed
        /// </summary>
        private string _file;
        public string File
        {
            get
            {
                return _file;
            }
            set
            {
                if (CheckPropertyChanged<string>("File", ref _file, ref value))
                {
                    if (File == string.Empty)
                    {
                        Overflow = false;
                    }
                    else
                    {
                        _forceReRun = true;
                        ReadFile();
                    }
                }
            }
        }

        /// <summary>
        /// The text to be analyzed, up to MAX_CONTENT_LENGTH
        /// </summary>
        private string _content;
        public string Content
        {
            get
            {
                return _content;
            }
            set
            {
                if (CheckPropertyChanged<string>("Content", ref _content, ref value))
                    _forceReRun = true;
            }
        }

        // Whether stemming should be done     
        private bool _doStemming;
        public bool DoStemming
        {
            get
            {
                _doStemming = Properties.Settings.Default.DoStemming;
                return _doStemming;
            }

            set
            {
                if (CheckPropertyChanged<bool>("DoStemming", ref _doStemming, ref value))
                    Properties.Settings.Default.DoStemming = value;
            }
        }

        /// <summary>
        /// Whether the list of stop words should be used
        /// </summary>       
        private bool _useStopWords;
        public bool UseStopWords
        {
            get
            {
                _useStopWords = Properties.Settings.Default.UseStopWords;
                return _useStopWords;
            }

            set
            {
                if (CheckPropertyChanged<bool>("UseStopWords", ref _useStopWords, ref value))
                    Properties.Settings.Default.UseStopWords = value;
            }
        }

        /// <summary>
        /// List for populating top N combo box
        /// </summary>
        public List<uint> TopNItems
        {
            get
            {
                uint[] arr1 = { 10, 20, 30, 40, 50, 100, 200, 300, 400, 500 };
                return new List<uint>(arr1);

            }
        
        }

        /// <summary>
        /// List for populating percentage combo box
        /// </summary>
        public List<uint> PercentItems
        {
            get
            {
                uint[] arr2 = { 1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100 };
                return new List<uint>(arr2);
            }
        }

        /// <summary>
        /// Top N words threshold
        /// </summary>
        private uint _topNThresh;
        public uint TopNThresh
        {
            get
            {
                _topNThresh = Properties.Settings.Default.TopNThresh;
                return _topNThresh;
            }

            set
            {
                if (CheckPropertyChanged<uint>("TopNThresh", ref _topNThresh, ref value))
                    Properties.Settings.Default.TopNThresh = value;
            }
        }

        /// <summary>
        /// Cumulative percentage threshold
        /// </summary>
        private uint _percentThresh;
        public uint PercentThresh
        {
            get
            {
                _percentThresh = Properties.Settings.Default.PercentThresh;
                return _percentThresh;
            }

            set
            {
                if (CheckPropertyChanged<uint>("PercentThresh", ref _percentThresh, ref value))
                    Properties.Settings.Default.PercentThresh = value;
            }
        }

        /// <summary>
        /// Whether to use a reference file
        /// </summary>        
        private bool _useReferenceFile;
        public bool UseReferenceFile
        {
            get
            {
                _useReferenceFile =  Properties.Settings.Default.UseReference;
                return _useReferenceFile;
            }

            set
            {
                if (CheckPropertyChanged<bool>("UseReferenceFile", ref _useReferenceFile, ref value))
                    Properties.Settings.Default.UseReference = value;
            }
        }

        /// <summary>
        /// The reference file path
        /// </summary> 
        private string _referenceFilePath;
        public string ReferenceFilePath
        {
            get
            {
                _referenceFilePath = Properties.Settings.Default.ReferenceFile;
                return _referenceFilePath;
            }

            set
            {
                if (CheckPropertyChanged<string>("ReferenceFilePath", ref _referenceFilePath, ref value))
                    Properties.Settings.Default.ReferenceFile = value;
            }
        }

        /// <summary>
        /// Total number of words
        /// </summary>
        private uint _totalWordCount;
        public uint TotalWordCount
        {
            get
            {
                return _totalWordCount;
            }
            set
            {
                CheckPropertyChanged<uint>("TotalWordCount", ref _totalWordCount, ref value);                    
            }
        }

        /// <summary>
        /// Output words
        /// </summary>
        private ListCollectionView _results;
        public ListCollectionView Results
        {
            get
            {
                return _results;
            }
            set
            {
                CheckPropertyChanged<ListCollectionView>("Results", ref _results, ref value);                    
            }
        }

        /// <summary>
        /// Whether any reference data was retrieved from the file
        /// </summary>
        private bool _noReferenceData;
        public bool NoReferenceData
        {
            get
            {
                return _noReferenceData;
            }
            private set
            {
                CheckPropertyChanged<bool>("NoReferenceData", ref _noReferenceData, ref value); 
            }
        }
        
        /// <summary>
        /// Whether the number of words in the input exceeds the display buffer size
        /// </summary>
        private bool _overflow;
        public bool Overflow 
        { 
            get
            {
                return _overflow;
            }
            private set
            {
                CheckPropertyChanged<bool>("Overflow", ref _overflow, ref value); 
            }
        }

        /// <summary>1
        /// Max length of content to display - 8GB (2 bytes/char)
        /// </summary>
        private const int MAX_CONTENT_DISPLAY_LENGTH = 4096*1024;
        #endregion

        #region Public Methods
        /// <summary>
        /// Constructor
        /// </summary>
        public MainViewModel(ICounter counter,
                             ICountFilter filter,
                             IFileDialogService dlgService,
                             IView window,                             
                             IUnityContainer container)
        {            
            _counter = counter;
            _filter = filter;
            _dlgService = dlgService;
            
            IUnityContainer childContainer = container.CreateChildContainer()
                .RegisterType<IView, StopWordsWindow>();     
            _stopWordsModel = childContainer.Resolve<IStopWordsViewModel>();            
            
            _computeCommand = new DelegateCommand<object>(Compute);
            _saveAsReferenceCommand = new DelegateCommand<object>(SaveAsReference);
            _editStopWordsCommand = new DelegateCommand<object>(EditStopWords);
            
            _totalWordCount = 0;
            _content = string.Empty;
            _file = string.Empty;
            _forceReRun = true;
            _overflow = false;
            _noReferenceData = true;

            window.SetModel(this);
            window.Show();

        }        
        #endregion

        #region Command Delegates
        /// <summary>
        /// Delegate for command
        /// </summary>       
        /// <param name="param">ignored</param>
        private void Compute(object param)
        {            
            if (_forceReRun)
            {
                uint total;
                Dictionary<string, uint> counts;

                if (Overflow)
                    _counter.RunFile(File, out total, out counts);
                else
                    _counter.RunText(Content, out total, out counts);

                _forceReRun = false;
                TotalWordCount = total;
                _rawCounts = counts;
            }

            List<WordCountInfo> finalCounts;
            bool refDataOK;
            CountOptions countOpt = new CountOptions(_doStemming, _useStopWords, _stopWordsModel.StopWords, _topNThresh, _percentThresh, _useReferenceFile, _referenceFilePath);
            _filter.Run(countOpt, _rawCounts, _totalWordCount, out finalCounts, out refDataOK);
            Results = new ListCollectionView(finalCounts);
            NoReferenceData = !refDataOK;
        }

        /// <summary>
        /// Delegate for command
        /// </summary>        
        /// <param name="param">ignored</param>
        private void SaveAsReference(object param)
        {
            string path = _dlgService.ReferenceFileDialog(true);
            if (!string.IsNullOrEmpty(path))
            {
                ReferenceDataFile refFile = new ReferenceDataFile();
                bool ok = refFile.Write(path, _rawCounts);
                if (!ok)
                    _dlgService.ErrorMessageBox(Properties.Resources.MessageWriteFail);
            }
        }

        /// <summary>
        /// Delegate for command
        /// </summary>
        /// <param name="param"></param>
        private void EditStopWords(object param)
        {
            _stopWordsModel.Run();        
        }

        #endregion

        #region Private Methods
        /// <summary>
        /// Read the text to be displayed from the file
        /// </summary>
        private void ReadFile()
        {
            StringBuilder charsRead = new StringBuilder();
            using (StreamReader sr = new StreamReader(_file))
            {
                int totalRead = 0;
                int numRead = 0;
                char[] c = new char[4096];
                Overflow = false;
                while (sr.Peek() >= 0 && !Overflow)
                {
                    if (totalRead >= MAX_CONTENT_DISPLAY_LENGTH)
                        Overflow = true;
                    else
                    {
                        numRead = sr.Read(c, 0, c.Length);
                        totalRead += numRead;
                        charsRead.Append(new string(c, 0, numRead));
                    }
                }
            }

            Content = charsRead.ToString();
        }
        #endregion
    }
}
